這篇文章有點長,步驟有點煩雜,覺得累的話請先喝杯水再來。
在 C/C++ 裡面,有些函式提供了很人性化的介面機制,叫 function pointer - 函式指標,只要在 MSDN 上原型裡面,參數看到是 PROC 結尾的,幾乎都是函式指標。只要是在 API 裡面出現的函式指標,那跟 struct 沒什麼二樣,有一定的函式規格,還要再去查這個函式指標的原型長怎樣。
我們以 EnumWindow 為例,這個 API 函式主要是在列舉目前 Windows 上的可見視窗,它的原型如下
BOOL WINAPI EnumWindows(
__in WNDENUMPROC lpEnumFunc,
__in LPARAM lParam
);
注意到了,第一個參數就是所謂的函式指標,這裡的用法有點步驟,等一下直接看範例碼會比較快
第二個參數 lParam 提供一些相關資訊。
剛說過了,API 裡面出現了函式指標了,於是我們再上 MSDN 去查 WNDENUMPROC lpEnumFunc 它的規格長怎樣,原型如下
BOOL CALLBACK EnumWindowsProc(
__in HWND hwnd,
__in LPARAM lParam
);
嗯,這個看起來簡單很多,第一個 HWND hwnd 是視窗 handle,第二個 lParam ,這又是提供另一些資料,我們不會用到。但要注意,這裡有傳回值是 BOOL。
好了,接下來用 autoit 去做會有一些步驟..
這裡先弄清楚,我們真正要用的是 EnumWindows(A) 這個函式,
但 EnumWindows 必須先自己寫一個 "符合標準,但是要自己寫的"
CALLBACK EnumWindowsProc (B) 函式,等於是要分二層,
先把裡面的 (B) 處理好,才能讓 (A) 去使用,於是要先處理的,是 CALLBACK 函式 - EnumWindowsProc
// ---------------------------------------------------------------------
// 步驟一 針對 EnumWindowsProc 寫一個自己的處理函式
EnumWindowsProc 這 function 型態輸入引數是 hwnd, lParam,傳回 BOOL我們 function 也不去動它,自己先寫一個 myEnumWindowProc,一開始會長得像這樣
; 自己的 CALLBACK 函式
Function myEnumWindowProc($hwnd, $lParam)
EndFunc
再來,剛說過了,這個函式的引數第一個是視窗的 handle ,第二個參數我們不在意,我們這自己寫的程式寫簡單一點,只要去秀出這個視窗的抬頭文字是什麼。 autoit 裡面,有視窗 handle 要取得它的文字是用 WinGetTitle,於是我們函式就變成這樣
; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
MsgBox(0, "", WinGetTitle($hWnd))
return 1
EndFunc
嗯,這裡要跟各位說,上面這個看起來好像沒問題,其實問題很大。因為 EnumWindows 這個函式有用到另一個函式 - EnumWindowsProc ,它會一直不斷不斷不斷不斷... 地一直找下一個視窗,直到myWindowProc 傳回0為止。意思是如果我上面一直傳回 1 的話, EnumWindowsProc 就會找不停,可能會找到當為止。所以必須再給它適合的終止條件
; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
$res = MsgBox(1, "", WinGetTitle($hWnd))
If $res = 2 Then Return 0 ; 取消點選,返回 0 結束列出動作
EndIf
Return 1 ; 返回 1 繼續列出
EndFunc
// ---------------------------------------------------------------------
// 步驟二 為自己新增的 CALLBACK 函式進行註冊
要使用上述自己的 CALLBACK 函式之前,記得用AutoIt 向 dll 註冊函式,註冊的時候再看一次 API 的原型
BOOL CALLBACK EnumWindowsProc(
__in HWND hwnd,
__in LPARAM lParam
);
傳回 BOOL(對應用 autoit 可以用 int 帶過),第一個是 hwnd,第二個是 lParam。於是我們使用 DllCallbackRegister 函式為 CALLBACK 系列的 API 進行註冊
$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam")
$FuncHandle = DllCallbackRegister ("自己寫好的參數名稱", "API 傳回形態", "API 參數1;API參數2;...")
於是現在整段程式碼就變這樣
; 註冊自己的 CALLBACK 函式
$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam")
; .....
; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
$res = MsgBox(1, "", WinGetTitle($hWnd))
If $res = 2 Then Return 0 ; 取消點選,返回 0 結束列出動作
EndIf
Return 1 ; 返回 1 繼續列出
EndFunc
// ---------------------------------------------------------------------
// 步驟三 使用 DllCall 呼叫 EnumWindows API
CALLBACK 函式都寫好、註冊完了,接下來總算可以叫 EnumWindows API 幫我們做事了。再看一次 EnumWindows 原型
BOOL WINAPI EnumWindows(
__in WNDENUMPROC lpEnumFunc,
__in LPARAM lParam
);
傳回 BOOL (autoit 用 it),參數一是我們剛剛寫好、註冊好的 $FundHandle,這裡要注意,之前有說過,只要是「指標」,我們的資料型態都用 "ptr",而變數一律要再調用 "DllCallbackGetPtr" 去取得該資料的位址(因為是指標,是傳送位址值),這裡也是一樣,只要是 CALLBACK ,就是函式指標,所以我們要傳的是 "一個函式的指標"。參數二直接用 lParam, 這個放什麼都沒差,直接塞一個垃圾也行。寫出來就像下面這樣
; 註冊自己的 CALLBACK 函式
$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam")
; 向 Dll 呼叫 EnumWindows,CALLBACK 函式使用自己寫的
DllCall("user32.dll", "int", "EnumWindows", _
"ptr", DllCallbackGetPtr($FuncHandle), "lparam", 0 )
; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
$res = MsgBox(1, "", WinGetTitle($hWnd))
If $res = 2 Then Return 0 ; 取消點選,返回 0 結束列出動作
EndIf
Return 1 ; 返回 1 繼續列出
EndFunc
// ---------------------------------------------------------------------
// 步驟四 最後再把已註冊的 CALLBACK 函式釋放掉
最後一個步驟是用 DllCallbackFree ,把我們剛剛註冊好的 $FuncHandle 釋放掉。整段完整原始碼如下
#include <GUIConstantsEx.au3>
; 註冊自己的 CALLBACK 函式
$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam")
; 向 Dll 呼叫 EnumWindows,CALLBACK 函式使用自己寫的
DllCall("user32.dll", "int", "EnumWindows", _
"ptr", DllCallbackGetPtr($FuncHandle), "lparam", 0 )
; 釋放 callback 函數
DllCallbackFree($FuncHandle)
; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
$res = MsgBox(1, "", WinGetTitle($hWnd))
If $res = 2 Then Return 0 ; 取消點選,返回 0 結束列出動作
EndIf
Return 1 ; 返回 1 繼續列出
EndFunc
這部份比較煩雜,如果有問題的話,可以先去看前面幾篇文章,這篇再看個二、三次應該就可以了。
留言列表